/*
 * Copyright (C) 2000-2005 Philips Electronics N.V.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file gnu_license.txt.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  
 * 
 */
#include "breaks.h"
#include "cfront.h"
#include "assert.h"

typedef struct context_s* context;

typedef struct context_s {
  statement brk;
  statement swtch;
  context   prev;
  statement owner;
} context_t;


#define CONTEXT_BREAK(c) ((c)->brk)
#define CONTEXT_SWITCH(c)((c)->swtch)
#define CONTEXT_PREV(c)  ((c)->prev)
#define CONTEXT_OWNER(c) ((c)->owner)
  
static struct s_cfront_TraverseTable relate_break_case_actions;
static struct s_void_cfront_TraverseTable check_switch_actions;

context copy_context(context cont)
{
  context res;

  res = Front_Alloc(sizeof(context_t));
  *res = *cont;
  return res;
}

void free_context(context cont)
{
  free(cont);
}

void List_statement_append_node(List_statement *l, statement n)
{
  Front_List_append_node((Front_List*)(l),(void*)(n));
}

static context Action_relate_to_statement(statement me, context cont)
{
  context new_cont;
  if (me == NULL) return cont;
  
  switch (statement_tag(me)) {
  case ForStat_kind:
  case DoStat_kind:
  case WhileStat_kind:
    /* All breaks encountered deeper in the AST relate to the current loop statement */
    new_cont = copy_context(cont);
    CONTEXT_PREV(new_cont) = cont;
    CONTEXT_OWNER(new_cont) = me;

    CONTEXT_BREAK(new_cont) = me;
    break;
  case SwitchStat_kind:
    /* All breaks and case encountered deeper in the AST relate to the current switch statement */
    new_cont = copy_context(cont);
    CONTEXT_PREV(new_cont) = cont;
    CONTEXT_OWNER(new_cont) = me;

    CONTEXT_BREAK(new_cont) = me;
    CONTEXT_SWITCH(new_cont) = me;
    break;
  default:
    new_cont = cont;
    break;
  }

  return new_cont;
}

static context Action_relate_to_statement_post(statement me, context cont)
{
  context new_cont;
  
  if (me == NULL) return cont;

  switch (statement_tag(me)) {
  case ForStat_kind:
  case DoStat_kind:
  case WhileStat_kind:
  case SwitchStat_kind:
    assert(CONTEXT_OWNER(cont) == me);
    new_cont = CONTEXT_PREV(cont);
    free_context(cont);
    break;
  default:
    new_cont = cont;
    break;
  }

  return new_cont;
}   

static context Action_relate_case(statement me, context cont)
{
  if (me == NULL) return cont;

  CaseStat_switchstat(me) = CONTEXT_SWITCH(cont);
  List_statement_append_node(&SwitchStat_cases(CONTEXT_SWITCH(cont)), me);

  return cont;
}

static context Action_relate_default(statement me, context cont)
{
  if (me == NULL) return cont;

  DefaultStat_switchstat(me) = CONTEXT_SWITCH(cont);
  SwitchStat_deflt(CONTEXT_SWITCH(cont)) = me;

  return cont;
}

static context Action_relate_break(statement me, context cont)
{
  if (me == NULL) return cont;

  BreakStat_context(me) = CONTEXT_BREAK(cont);

  return cont;
}

void Relate_breaks_and_cases(cfront root) 
{
  context_t init_context;

  CONTEXT_BREAK(&init_context) = NULL;
  CONTEXT_SWITCH(&init_context) = NULL;
  CONTEXT_PREV(&init_context) = NULL;
  
  relate_break_case_actions.traversal = Traverse_Down;
  relate_break_case_actions.action_statement[PRE_ACTION] = (void *) Action_relate_to_statement;
  relate_break_case_actions.traversal = Traverse_Down;
  relate_break_case_actions.action_statement[POST_ACTION] = (void *) Action_relate_to_statement_post;
  relate_break_case_actions.action_CaseStat[PRE_ACTION] = (void *) Action_relate_case;
  relate_break_case_actions.action_DefaultStat[PRE_ACTION] = (void *) Action_relate_default;
  relate_break_case_actions.action_BreakStat[PRE_ACTION] = (void *) Action_relate_break;

  Traverse_cfront(root, &relate_break_case_actions, &init_context);
}

/**********************************************************************/

#if 0
static statement Action_find_case(statement me, statement casestat)
{
  if (casestat != NULL || me == NULL) return casestat;

  return me;
}
#endif

/* Check that all cases of a switch are one the same level */
static void Action_check_switch(statement me)
{
  List_statement body;
  List_statement cases;
  statement stat;
  Bool error_printed;
  
  error_printed = FALSE;

  if (statement_tag(SwitchStat_body(me)) != compound_statement_kind) {
    InputWarning(expression_loc(me), TRUE, 0, "Amber: compound statement expected after switch");
    return;
  }

#if 0
  body = compound_statement_body(SwitchStat_body(me));

  cases = SwitchStat_cases(me);
  
  for (; body != NULL; body = List_statement_next(body)) {
    stat = List_statement_data(body);
    
    if (statement_tag(stat) == CaseStat_kind) {
      if (List_statement_data(cases) != stat) {
	InputWarning(expression_loc(me), TRUE, 0, "Amber: cases of switch not all on the correct level");
	error_printed = TRUE;
	break;
      }
      cases = List_statement_next(cases);
    }
  }

  if (cases != NULL && !error_printed) {
    InputWarning(expression_loc(me), TRUE, 0, "Amber: cases of switch not all on the correct level");
    error_printed = TRUE;
  }
#endif
  return;
}

void Check_switches(cfront root)
{
  check_switch_actions.action_SwitchStat[PRE_ACTION] = Action_check_switch;
  (void) Traverse_void_cfront(root, &check_switch_actions);
}
